package containerd

import (
	"bytes"
	"context"
	_ "embed"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/pkg/errors"
	configv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	yaml2 "sigs.k8s.io/yaml"

	"gopkg.openfuyao.cn/bkeadm/pkg/config"
	econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/k8s"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

var (
	//go:embed config.toml
	configToml string
	//go:embed containerd_crd.yaml
	containerdCrd []byte
	//go:embed containerd_default.yaml
	containerdDefault       []byte
	defaultRuntime          = "runc"
	defaultInstallDirectory = "/"
	cniDirectory            = "/opt/cni/bin"
)

func applyContainerdCrd() error {
	var err error
	if global.K8s == nil {
		global.K8s, err = k8s.NewKubernetesClient("")
		if err != nil {
			return err
		}
	}

	containerdCrdFile := fmt.Sprintf("%s/tmpl/containerd_crd.yaml", global.Workspace)
	err = os.WriteFile(containerdCrdFile, containerdCrd, utils.DefaultFilePermission)
	if err != nil {
		return err
	}
	time.Sleep(1 * time.Second)

	log.BKEFormat(log.INFO, "Install Containerd CRD...")
	err = global.K8s.InstallYaml(containerdCrdFile, nil, "")
	if err != nil {
		return err
	}

	return nil
}

func applyContainerdDefault(domain string) error {
	runtimeParam := map[string]string{}
	sandbox, offline := config.GenerateControllerParam(domain)
	runtimeParam["sandbox"] = sandbox
	runtimeParam["offline"] = offline

	tmpl, err := template.New("containerd").Parse(string(containerdDefault))
	if err != nil {
		return fmt.Errorf("parse containerd default failed: %s", err.Error())
	}

	var buf bytes.Buffer
	if err = tmpl.Execute(&buf, runtimeParam); err != nil {
		return fmt.Errorf("render containerd default failed: %s", err.Error())
	}

	conf := &configv1beta1.ContainerdConfig{}
	if err = yaml2.Unmarshal(buf.Bytes(), conf); err != nil {
		return fmt.Errorf("unmarshal containerd default failed: %s", err.Error())
	}

	namespace := corev1.Namespace{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Namespace",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: conf.Namespace,
		},
		Spec:   corev1.NamespaceSpec{},
		Status: corev1.NamespaceStatus{},
	}

	err = global.K8s.CreateNamespace(&namespace)
	if err != nil {
		log.BKEFormat(log.WARN, err.Error())
	}

	containerdDefaultFile := fmt.Sprintf("%s/tmpl/containerd_default.yaml", global.Workspace)
	err = os.WriteFile(containerdDefaultFile, containerdDefault, utils.DefaultFilePermission)
	if err != nil {
		return err
	}
	time.Sleep(1 * time.Second)

	log.BKEFormat(log.INFO, fmt.Sprintf("Submit containerd default yaml to the cluster"))
	err = global.K8s.InstallYaml(containerdDefaultFile, runtimeParam, "")
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to install containerd default, %v", err))
		return nil
	}
	log.BKEFormat(log.INFO, fmt.Sprintf("Submit the containerd configuration to the cluster"))

	return nil
}

func ApplyContainerdCfg(domain string) error {
	err := applyContainerdCrd()
	if err != nil {
		return fmt.Errorf("apply containerd crd failed: %s", err.Error())
	}

	err = applyContainerdDefault(domain)
	if err != nil {
		return fmt.Errorf("apply containerd default failed: %s", err.Error())
	}

	log.BKEFormat(log.INFO, "Apply containerd crd and default success")

	return nil
}

func getPlatform() string {
	switch runtime.GOARCH {
	case "amd64":
		return "linux/amd64"
	case "arm64":
		return "linux/arm64"
	case "arm":
		return "linux/arm/v7"
	default:
		return "linux/amd64"
	}
}

func executeTemplateWithFile(tplContent, tplName string, data interface{}, file *os.File) error {
	// 解析模板
	tmpl, err := template.New(tplName).Parse(tplContent)
	if err != nil {
		return fmt.Errorf("parse template %s failed: %w", tplName, err)
	}

	// 执行模板
	if err := tmpl.Execute(file, data); err != nil {
		return fmt.Errorf("execute template %s failed: %w", tplName, err)
	}

	return nil
}

func Install(domain, runtimeStorage, containerdFile, caFile string) error {
	err := utils.UnTar(containerdFile, defaultInstallDirectory)
	if err != nil {
		return err
	}
	runtimeParam := map[string]string{
		"runtime": defaultRuntime,

		"repo":     domain, //含有端口号
		"dataRoot": runtimeStorage,
		"caFile":   caFile, // 传入用户指定的CA证书路径
	}
	sandbox, offline := config.GenerateControllerParam(domain)
	runtimeParam["sandbox"] = sandbox
	runtimeParam["offline"] = offline
	runtimeParam["platform"] = getPlatform()
	log.BKEFormat(log.INFO, fmt.Sprintf("containerd sandbox image: %s", runtimeParam["sandbox"]))

	// Render configuration file
	if err = writeConfigToDisk(runtimeParam); err != nil {
		return err
	}
	// 新增：创建 hosts.toml 配置
	if err = createHostsTOML(runtimeParam); err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Failed to create hosts.toml: %v", err))
		return err
	}
	// enable and start containerd
	err = global.Command.ExecuteCommand("systemctl", "enable", "containerd")
	if err != nil {
		return err
	}

	// Start the containerd
	err = global.Command.ExecuteCommand("systemctl", "start", "containerd")
	if err != nil {
		return err
	}
	log.BKEFormat(log.INFO, "wait for containerd to start")
	if err = waitContainerdReady(); err != nil {
		return err
	}
	return nil
}

func waitContainerdReady() error {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	err := wait.PollImmediateUntil(5*time.Second, func() (bool, error) {
		log.BKEFormat(log.INFO, "Waiting for containerd to be ready")
		_, err := econd.NewContainedClient()
		if err == nil {
			return true, nil
		}
		log.BKEFormat(log.WARN, fmt.Sprintf("containerd is not available: %v", err))
		return false, nil
	}, ctx.Done())
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to wait containerd available: %v", err))
		return errors.Wrapf(err, "failed to wait containerd available")
	}
	return nil
}

func writeConfigToDisk(runtimeParam map[string]string) error {
	// Render configuration file
	f, err := os.OpenFile(fmt.Sprintf("%s%s", defaultInstallDirectory, "etc/containerd/config.toml"), os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer func() {
		_ = f.Close()
	}()
	tpl, err := template.New("config.toml").Parse(configToml)
	return tpl.Execute(f, runtimeParam)
}

func createOfflineSpecialHostsTOML(certsDir string) error {
	offlineRegistry := "0.0.0.0:40443"
	registryDir := filepath.Join(certsDir, offlineRegistry)
	hostsTOMLPath := filepath.Join(registryDir, "hosts.toml")

	if err := os.MkdirAll(registryDir, utils.DefaultDirPermission); err != nil {
		return fmt.Errorf("create %s dir failed: %v", offlineRegistry, err)
	}

	hostsContent := `server = "https://0.0.0.0:40443"
[host."https://deploy.bocloud.k8s:40443"]
  capabilities = ["pull", "resolve", "push"]
  skip_verify = true
`

	f, err := os.OpenFile(hostsTOMLPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, utils.DefaultFilePermission)
	if err != nil {
		return fmt.Errorf("create %s hosts.toml failed: %v", offlineRegistry, err)
	}

	if _, err = f.WriteString(hostsContent); err != nil {
		err2 := f.Close()
		return fmt.Errorf("render %s hosts.toml failed: %w and %w", offlineRegistry, err2, err)
	}

	err = f.Close()
	if err != nil {
		return fmt.Errorf("close %s hosts.toml failed: %v", offlineRegistry, err)
	}

	log.BKEFormat(log.INFO, fmt.Sprintf("Created offline special hosts.toml: %s", hostsTOMLPath))
	return nil
}

func createHostsTOML(runtimeParam map[string]string) error {
	repo := runtimeParam["repo"]
	repoWithNoPort := strings.Split(repo, ":")[0]
	offline := runtimeParam["offline"]
	certsDir := "/etc/containerd/certs.d"
	registries := []string{repo, repoWithNoPort} //同时包含“加端口”和“不加端口”的 registry

	if offline == "true" {
		publicRegistries := []string{
			"docker.io", "registry.k8s.io", "k8s.gcr.io", "ghcr.io", "quay.io", "gcr.io", "cr.openfuyao.cn",
		}
		registries = append(registries, publicRegistries...)
		log.BKEFormat(log.INFO, "Offline mode: configuring public registry redirects")
		if err := createOfflineSpecialHostsTOML(certsDir); err != nil {
			return fmt.Errorf("offline special registry config failed: %v", err)
		}
	}

	for _, registry := range registries {
		registryDir := filepath.Join(certsDir, registry)
		if err := os.MkdirAll(registryDir, utils.DefaultDirPermission); err != nil {
			return fmt.Errorf("create %s dir failed: %v", registry, err)
		}
		data := struct {
			Repo     string
			Registry string
			Offline  string
			CAFile   string // 私有仓库CA证书是否存在
		}{
			Repo:     repo,
			Registry: registry,
			Offline:  offline,
			CAFile:   runtimeParam["caFile"], // 从runtimeParam传入CA证书路径是否存在的标识
		}

		hostsTpl := `# 私有镜像仓配置：{{.Registry}}
server = "https://{{.Registry}}"  # 直接指向私有仓库的域名和端口
[host."https://{{.Registry}}"]
  capabilities = ["pull", "resolve", "push"]  # 支持拉取、解析、推送
  {{if .CAFile}}ca = "/etc/containerd/certs.d/{{.Registry}}/ca.crt"{{end}}  # 若有CA证书，指定证书路径
  skip_verify = {{if .CAFile}}false{{else}}true{{end}}  # 有CA证书则不跳过验证，否则跳过
`

		hostsPath := filepath.Join(registryDir, "hosts.toml")
		f, err := os.OpenFile(hostsPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, utils.DefaultFilePermission)
		if err != nil {
			return fmt.Errorf("create %s hosts.toml failed: %v", registry, err)
		}
		if err := executeTemplateWithFile(hostsTpl, "baseHosts", data, f); err != nil {
			if closeErr := f.Close(); closeErr != nil {
				return fmt.Errorf("render %s hosts.toml failed: %w and %w", registry, err, closeErr)
			}
			return fmt.Errorf("process template for %s failed: %w", registry, err)
		}
		if err = f.Close(); err != nil {
			return fmt.Errorf("close %s hosts.toml failed: %v", registry, err)
		}
	}
	if offline == "true" {
		log.BKEFormat(log.INFO, fmt.Sprintf("Offline mode configured: public traffic redirects to %s", repo))
	}
	return nil
}

func CniPluginInstall(cniPluginFile string) error {
	bridge := fmt.Sprintf("%s/bridge", cniDirectory)
	if utils.Exists(bridge) {
		return nil
	}

	_ = os.MkdirAll(cniDirectory, 0644)
	err := utils.UnTar(cniPluginFile, cniDirectory)
	if err != nil {
		return err
	}
	return nil
}
